package org.shapeoforion.utils.timer;

import org.apache.commons.logging.Log;
import org.shapeoforion.log.MyLogger;

/**
 * Abstract functionality for a sleeper.
 *
 * @author jps
 * @version 0.1
 */
public abstract class AbstractSleeper implements Sleeper {

    private static final Log LOG = MyLogger.getInstance(AbstractSleeper.class);

    /**
     * Sleep a while.
     *
     * @param millis the number of milliseconds to sleep
     */
    public final void sleep(final long millis) {

        double dmillis = (double)millis;
        double actualSleep = (dmillis + (dmillis * error));

        if(LOG.isTraceEnabled()) { // yes this looks stupid but i really meant it
            LOG.debug("sleeping: "+millis+"ms, actually: " + actualSleep);
        }
        
        doSleep((long)actualSleep);
    }

    protected abstract void doSleep(final long millis);

    protected AbstractSleeper() {

        performSleepTiming();
    }

    private void performSleepTiming() {

        LOG.info("timing sleep delay...");
        long totalDelta = 0, totalSleep = 0;
        for (long sleep : SLEEPING) {
            totalDelta += timeDelay(sleep);
            totalSleep += sleep;
        }
        error = ((float) totalDelta / (float) totalSleep);

        if (LOG.isDebugEnabled()) {
            LOG.debug("overslept for " + totalDelta + "ms");
            LOG.debug("+ on a total of " + totalSleep + "ms");
            LOG.debug("+ error=" + (error*100) + " %");
        }
    }

    /**
     * Time the milliseconds that were actually slept using the implementations sleep method.
     *
     * @param sleep sleep time to test
     * @return the delta
     */
    private long timeDelay(long sleep) {

        LOG.debug("sleeping " + sleep + "ms");
        final long start = System.nanoTime();
        doSleep(sleep);
        final long ms = (System.nanoTime() - start) / 1000000L;
        LOG.debug("actually slept:" + ms);
        final long delta = ms - sleep;
        LOG.debug("delta=" + delta);

        return delta;
    }

    /** used to calculate the sleep correction. */
    private double error = 0.0;

    /** contains the values used for testing the sleep timer. */
    private static final long[] SLEEPING = {500, 250};
}
